package org.batatasframework.support.definition.impl;

import java.util.List;

import org.batatasframework.common.NumberLimit;
import org.batatasframework.common.StringLimit;
import org.batatasframework.support.database.DatabaseType;
import org.batatasframework.support.database.FieldShiftable;
import org.batatasframework.support.database.metadatas.FieldMetadata;
import org.batatasframework.support.database.sql.TableInstance;
import org.batatasframework.support.database.sql.spi.TableCriterion;
import org.batatasframework.support.database.sql.where.ArrayQueryFragment;
import org.batatasframework.support.database.sql.where.QueryFragmentAppender;
import org.batatasframework.support.database.sql.where.ShiftQueryFragment;
import org.batatasframework.support.database.sql.where.DefaultQueryFragment;
import org.batatasframework.support.database.sql.where.ZeroQueryFragment;
import org.batatasframework.support.database.sql.where.ZeroQueryFragmentDefault;
import org.batatasframework.support.database.sql.where.ZeroQueryFragmentNumberLimit;
import org.batatasframework.support.database.sql.where.ZeroQueryFragmentStringLimit;
import org.batatasframework.support.definition.FieldDefinitionSourcable;
import org.batatasframework.support.definition.FieldDefinitionQueriable;
import org.batatasframework.support.definition.QueryDefinition;
import org.batatasframework.support.definition.TableDefinition;
import org.batatasframework.support.definition.TableDefinitionResource;
import org.batatasframework.support.event.HttpRequestAccessor;
import org.batatasframework.support.searchable.FieldQueryed;
import org.batatasframework.support.searchable.FieldQueryedDefault;
import org.batatasframework.support.searchable.FieldQueryedOptional;
import org.batatasframework.support.type.JavaType;
import org.batatasframework.support.type.OptionalIterable;
import org.batatasframework.support.type.OptionalIterable.OptionalEntry;

import cn.bonoon.kernel.support.IOperator;
import cn.bonoon.kernel.util.Opt;

public abstract class AbstractSingleFieldDefinition extends AbstractFieldDefinition implements FieldDefinitionSourcable, FieldDefinitionQueriable{
	
	private static final Opt[] single_opts = { Opt.NULL, Opt.NN, Opt.EQ, Opt.NE, Opt.IN, Opt.NI };
	private static final Opt[] shift_opts = { Opt.EQ, Opt.NE, Opt.IN, Opt.NI };
		
	private JavaType javaType;
	
	private QueryDefinition queryDefinition;
	private boolean queryable = true;
	
	protected AbstractSingleFieldDefinition(String key, TableDefinition searchableContext) {
		super(key, searchableContext);
	}
	
	/** 如果不需要生成查询条件，只作为查询结果集返回的，则可以设置"queryable=false" */
	public void setQueryable(boolean queryable) {
		this.queryable = queryable;
	}
	
	@Override
	public boolean isQueryable() {
		return queryable;
	}

	@Override
	public QueryDefinition getQueryDefinition() {
		if(null == queryDefinition){
			Object format = getFormat();
			if(format instanceof FieldShiftable){
				FieldShiftable fs = (FieldShiftable)format;
				if(fs.isMultiple()){
					/* 可以多选的情况 */
					queryDefinition = new ShiftFieldQueryDefinition(fs.getSources(), new NumberLimitZeroQueryFragmentBuilder(fs));
				}else{
					queryDefinition = new DefaultFieldQueryDefinition(fs.getSources(), DatabaseType.NUMBER, single_opts, new NumberLimitZeroQueryFragmentBuilder(fs));
				}
			}else{
				List<? extends OptionalEntry> optional = null;
				Opt[] opts;
				DatabaseType databaseType = getSourceField().getDataType();
				ZeroQueryFragmentBuilder zeroQueryFragmentBuilder;
				if(format instanceof OptionalIterable){
					optional = ((OptionalIterable)format).getSources();
					
					opts = single_opts;
				}else{
					opts = databaseType.getOpts();
				}
				
				if(format instanceof NumberLimit){
					zeroQueryFragmentBuilder = new NumberLimitZeroQueryFragmentBuilder((NumberLimit)format);
				}else if(format instanceof StringLimit){
					zeroQueryFragmentBuilder = new StringLimitZeroQueryFragmentBuilder((StringLimit)format);
				}else{
					zeroQueryFragmentBuilder = new DefaultZeroQueryFragmentBuilder();
				}
				
				queryDefinition = new DefaultFieldQueryDefinition(optional, databaseType, opts, zeroQueryFragmentBuilder);
			}
		}
		return queryDefinition;
	}

	public JavaType getJavaType() {
		return javaType;
	}

	public void setJavaType(JavaType javaType) {
		this.javaType = javaType;
	}

	protected class ShiftFieldQueryDefinition extends AbstractInternalQueryDefinition{
		
		public ShiftFieldQueryDefinition(List<? extends OptionalEntry> optional, ZeroQueryFragmentBuilder zeroQueryFragmentBuilder) {
			super(optional, zeroQueryFragmentBuilder);
		}

		@Override
		public DatabaseType getDataType() {
			return DatabaseType.NUMBER;
		}
		
		@Override
		public Opt[] getOpts() {
			return shift_opts;
		}
		
		@Override
		protected void buildQuery(QueryFragmentAppender queryAppend, TableInstance tableSource, Opt opt, String[] values) throws Exception {
			if(opt.getValueCount() == 0){
				queryAppend.addQuery(zeroQueryFragmentBuilder.buildZeroQueryFragment(tableSource, opt));
				return;
			}
			
			if(null == values) return;
			
			long[] lvs = new long[values.length];
			int i = 0;
			for(String val : values){
				if(val.equals("NULL")){
					return;
				}
				lvs[i++] = Long.parseLong(val);
			}
			queryAppend.addQuery(new ShiftQueryFragment(
					tableSource, 
					getSourceField(), opt, lvs, 
					zeroQueryFragmentBuilder.buildZeroQueryFragment(tableSource, opt)));
		}
	}
	
	protected abstract class AbstractInternalQueryDefinition implements QueryDefinition, FieldDefinitionSourcable{
		protected final ZeroQueryFragmentBuilder zeroQueryFragmentBuilder;
		
		protected final List<? extends OptionalEntry> optional;

		public AbstractInternalQueryDefinition(
				List<? extends OptionalEntry> optional, 
				ZeroQueryFragmentBuilder zeroQueryFragmentBuilder){
			this.optional = optional;
			this.zeroQueryFragmentBuilder = zeroQueryFragmentBuilder;
		}

		@Override
		public String getTitle() {
			return title;
		}
		
		@Override
		public TableDefinitionResource getSourceTable() {
			return AbstractSingleFieldDefinition.this.getSourceTable();
		}

		@Override
		public FieldMetadata getSourceField() {
			return AbstractSingleFieldDefinition.this.getSourceField();
		}

		@Override
		public void buildQuery(QueryFragmentAppender queryAppend, TableCriterion tableCriterion, HttpRequestAccessor paramAccessor, String key) throws Exception {
			buildQuery(queryAppend, tableCriterion.safeLoadSource(tableCriterion, getSourceTable(), true), paramAccessor, key);
		}

		@Override
		public void buildQuery(QueryFragmentAppender queryAppend, TableInstance tableSource, HttpRequestAccessor paramAccessor, String key) throws Exception {
			String[] values = paramAccessor.getStringArray("value_", key);
			
			Opt opt = Opt.valueOf(paramAccessor.getString("opt_", key));
			if(opt == null) opt = Opt.EQ;
			buildQuery(queryAppend, tableSource, opt, values);
		}
		
		protected abstract void buildQuery(QueryFragmentAppender queryAppend, TableInstance tableSource, Opt opt, String[] values) throws Exception;
		
		private FieldQueryedDefault fqCache;
		
		@Override
		public void buildFieldQueryed(List<FieldQueryed> queryColumn, String key, IOperator operator, boolean forceRefresh) {
			if(null == fqCache || forceRefresh){
				int type = getDataType().ordinal();
				if(null != optional && !optional.isEmpty()){
					fqCache = new FieldQueryedOptional(key, title, type, optional);
				}else{
					fqCache = new FieldQueryedDefault(key, title, type);
				}
				fqCache.set(getOpts());
			}
			queryColumn.add(fqCache);
		}
	}
	
	protected class DefaultFieldQueryDefinition extends AbstractInternalQueryDefinition{
		
		private final Opt[] opts;
		private final DatabaseType databaseType;
		
		public DefaultFieldQueryDefinition(List<? extends OptionalEntry> optional, DatabaseType databaseType, Opt[] opts, ZeroQueryFragmentBuilder zeroQueryFragmentBuilder){
			super(optional, zeroQueryFragmentBuilder);
			this.databaseType = databaseType;
			this.opts = opts;
		}

		@Override
		public DatabaseType getDataType() {
			return databaseType;
		}
		
		@Override
		public Opt[] getOpts() {
			return opts;
		}
		
		@Override
		protected void buildQuery(QueryFragmentAppender queryAppend, TableInstance tableSource, Opt opt, String[] values) throws Exception {
			if(opt.getValueCount() == 0){
				queryAppend.addQuery(new ZeroQueryFragmentDefault(tableSource, getSourceField(), opt));
			}

			if(opt.getValueCount() == 1){
				String value = null;
				if(null != values && values.length > 0){
					value = values[0];
				}
				queryAppend.addQuery(new DefaultQueryFragment(tableSource, getSourceField(), opt, value, zeroQueryFragmentBuilder.buildZeroQueryFragment(tableSource, opt)));
			}

			if(opt.getValueCount() > 1){
				queryAppend.addQuery(new ArrayQueryFragment(tableSource, getSourceField(), opt, values));
			}
		}
	}
	
	public interface ZeroQueryFragmentBuilder{
		ZeroQueryFragment buildZeroQueryFragment(TableInstance tableSource, Opt opt);
	}
	public class DefaultZeroQueryFragmentBuilder implements ZeroQueryFragmentBuilder{

		@Override
		public ZeroQueryFragment buildZeroQueryFragment(TableInstance tableSource, Opt opt) {
			return new ZeroQueryFragmentDefault(tableSource, getSourceField(), opt);
		}
		
	}
	
	public class NumberLimitZeroQueryFragmentBuilder implements ZeroQueryFragmentBuilder{
		private final NumberLimit numberLimit;
		
		public NumberLimitZeroQueryFragmentBuilder(NumberLimit numberLimit){
			this.numberLimit = numberLimit;
		}
		
		@Override
		public ZeroQueryFragment buildZeroQueryFragment(TableInstance tableSource, Opt opt) {
			return new ZeroQueryFragmentNumberLimit(tableSource, getSourceField(), opt, numberLimit);
		}
		
	}
	
	private class StringLimitZeroQueryFragmentBuilder implements ZeroQueryFragmentBuilder{
		private final StringLimit stringLimit;
		
		public StringLimitZeroQueryFragmentBuilder(StringLimit stringLimit){
			this.stringLimit = stringLimit;
		}

		@Override
		public ZeroQueryFragment buildZeroQueryFragment(TableInstance tableSource, Opt opt) {
			return new ZeroQueryFragmentStringLimit(tableSource, getSourceField(), opt, stringLimit);
		}
	}
}
